package org.jsets.fastboot.persistent.impl;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.jsets.fastboot.persistent.config.PersistentProperties;
import org.jsets.fastboot.persistent.enums.Algorithm;
import org.jsets.fastboot.persistent.IEncryptService;
import com.google.common.collect.Maps;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.digest.DigestAlgorithm;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.crypto.digest.HmacAlgorithm;
import cn.hutool.crypto.digest.SM3;
import cn.hutool.crypto.symmetric.SM4;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;

public class EncryptService implements IEncryptService {

	private final PersistentProperties properties;
	private final Map<String, SymmetricCrypto> symmetricCryptos;
	private final ReentrantReadWriteLock lock;
	private RSA rsa;
	private SM2 sm2;
	private SM3 sm3;
	private SM4 sm4;

	public EncryptService(PersistentProperties properties) {
		this.properties = properties;
		this.symmetricCryptos = Maps.newHashMap();
		this.lock = new ReentrantReadWriteLock();
	}

	@Override
	public String encrypt(Algorithm algorithm, String plaintext) {
		switch (algorithm) {
		case AES:
			return getSymmetricCrypto(SymmetricAlgorithm.AES).encryptHex(plaintext.getBytes());
		case DES:
			return getSymmetricCrypto(SymmetricAlgorithm.DES).encryptHex(plaintext.getBytes());
		case Blowfish:
			return getSymmetricCrypto(SymmetricAlgorithm.Blowfish).encryptHex(plaintext.getBytes());
		case DESede:
			return getSymmetricCrypto(SymmetricAlgorithm.DESede).encryptHex(plaintext.getBytes());
		case RC2:
			return getSymmetricCrypto(SymmetricAlgorithm.RC2).encryptHex(plaintext.getBytes());
		case PBEWithMD5AndDES:
			return getSymmetricCrypto(SymmetricAlgorithm.PBEWithMD5AndDES).encryptHex(plaintext.getBytes());
		case PBEWithSHA1AndDESede:
			return getSymmetricCrypto(SymmetricAlgorithm.PBEWithSHA1AndDESede).encryptHex(plaintext.getBytes());
		case PBEWithSHA1AndRC2_40:
			return getSymmetricCrypto(SymmetricAlgorithm.PBEWithSHA1AndRC2_40).encryptHex(plaintext.getBytes());
		case RSA:
			return getRsa().encryptHex(plaintext.getBytes(), KeyType.PrivateKey);
		case MD5:
			return DigestUtil.md5Hex(plaintext);
		case SHA1:
			return DigestUtil.sha1Hex(plaintext);
		case SHA256:
			return DigestUtil.sha256Hex(plaintext);
		case SHA384:
			return DigestUtil.digester(DigestAlgorithm.SHA384).digestHex(plaintext);
		case SHA512:
			return DigestUtil.digester(DigestAlgorithm.SHA512).digestHex(plaintext);
		case HmacMD5:
			Objects.requireNonNull(this.properties.getKey(), "秘钥[key]不能为空");
			return DigestUtil.hmac(HmacAlgorithm.HmacMD5, this.properties.getKey().getBytes())
					.digestHex(plaintext.getBytes());
		case HmacSHA1:
			Objects.requireNonNull(this.properties.getKey(), "秘钥[key]不能为空");
			return DigestUtil.hmac(HmacAlgorithm.HmacSHA1, this.properties.getKey().getBytes())
					.digestHex(plaintext.getBytes());
		case HmacSHA256:
			Objects.requireNonNull(this.properties.getKey(), "秘钥[key]不能为空");
			return DigestUtil.hmac(HmacAlgorithm.HmacSHA256, this.properties.getKey().getBytes())
					.digestHex(plaintext.getBytes());
		case HmacSHA384:
			Objects.requireNonNull(this.properties.getKey(), "秘钥[key]不能为空");
			return DigestUtil.hmac(HmacAlgorithm.HmacSHA384, this.properties.getKey().getBytes())
					.digestHex(plaintext.getBytes());
		case HmacSHA512:
			Objects.requireNonNull(this.properties.getKey(), "秘钥[key]不能为空");
			return DigestUtil.hmac(HmacAlgorithm.HmacSHA512, this.properties.getKey().getBytes())
					.digestHex(plaintext.getBytes());
		case SM2:
			return this.getSm2().encryptHex(plaintext, KeyType.PublicKey);
		case SM3:
			return this.getSm3().digestHex(plaintext);
		case SM4:
			return this.getSm4().encryptHex(plaintext);
		default:
			break;
		}
		return null;
	}

	@Override
	public String decrypt(Algorithm algorithm, String ciphertext) {
		switch (algorithm) {
		case AES:
			return getSymmetricCrypto(SymmetricAlgorithm.AES).decryptStr(ciphertext);
		case DES:
			return getSymmetricCrypto(SymmetricAlgorithm.DES).decryptStr(ciphertext);
		case Blowfish:
			return getSymmetricCrypto(SymmetricAlgorithm.Blowfish).decryptStr(ciphertext);
		case DESede:
			return getSymmetricCrypto(SymmetricAlgorithm.DESede).decryptStr(ciphertext);
		case RC2:
			return getSymmetricCrypto(SymmetricAlgorithm.RC2).decryptStr(ciphertext);
		case PBEWithMD5AndDES:
			return getSymmetricCrypto(SymmetricAlgorithm.PBEWithMD5AndDES).decryptStr(ciphertext);
		case PBEWithSHA1AndDESede:
			return getSymmetricCrypto(SymmetricAlgorithm.PBEWithSHA1AndDESede).decryptStr(ciphertext);
		case PBEWithSHA1AndRC2_40:
			return getSymmetricCrypto(SymmetricAlgorithm.PBEWithSHA1AndRC2_40).decryptStr(ciphertext);
		case RSA:
			return getRsa().decryptStr(ciphertext, KeyType.PublicKey);
		case SM2:
			return this.getSm2().decryptStr(ciphertext, KeyType.PrivateKey);
		case SM4:
			return this.getSm4().decryptStr(ciphertext);
		default:
			break;
		}
		return ciphertext;
	}

	private SymmetricCrypto getSymmetricCrypto(SymmetricAlgorithm algorithm) {
		SymmetricCrypto symmetricCrypto = null;
		this.lock.readLock().lock();
		symmetricCrypto = this.symmetricCryptos.get(algorithm.getValue());
		this.lock.readLock().unlock();
		if (null != symmetricCrypto) {
			return symmetricCrypto;
		}

		Objects.requireNonNull(this.properties.getKey(), "秘钥[key]不能为空");
		if (this.properties.getKey().length() != 16) {
			throw new IllegalArgumentException("秘钥[key]的长度必须为16");
		}

		this.lock.writeLock().lock();
		try {
			if (this.symmetricCryptos.containsKey(algorithm.getValue())) {
				return this.symmetricCryptos.get(algorithm.getValue());
			}
			symmetricCrypto = new SymmetricCrypto(algorithm, this.properties.getKey().getBytes());
			this.symmetricCryptos.put(algorithm.getValue(), symmetricCrypto);
			return symmetricCrypto;
		} finally {
			this.lock.writeLock().unlock();
		}
	}

	private RSA getRsa() {
		if (null != this.rsa) {
			return this.rsa;
		}

		Objects.requireNonNull(this.properties.getPublicKey(), "公钥[publicKey]不能为空");
		Objects.requireNonNull(this.properties.getPrivateKey(), "私钥[privateKey]不能为空");

		this.lock.writeLock().lock();
		try {
			if (null != this.rsa) {
				return this.rsa;
			}
			this.rsa = new RSA(this.properties.getPrivateKey(), this.properties.getPublicKey());
			return this.rsa;
		} finally {
			this.lock.writeLock().unlock();
		}
	}

	private SM2 getSm2() {
		if (null != this.sm2) {
			return this.sm2;
		}

		Objects.requireNonNull(this.properties.getPublicKey(), "公钥[publicKey]不能为空");
		Objects.requireNonNull(this.properties.getPrivateKey(), "私钥[privateKey]不能为空");

		this.lock.writeLock().lock();
		try {
			if (null != this.sm2) {
				return this.sm2;
			}
			this.sm2 = new SM2(this.properties.getPrivateKey(), this.properties.getPublicKey());
			return this.sm2;
		} finally {
			this.lock.writeLock().unlock();
		}
	}

	private SM3 getSm3() {
		if (null != this.sm3) {
			return this.sm3;
		}

		this.lock.writeLock().lock();
		try {
			if (null != this.sm3) {
				return this.sm3;
			}
			this.sm3 = new SM3();
			return this.sm3;
		} finally {
			this.lock.writeLock().unlock();
		}
	}

	private SM4 getSm4() {
		if (null != this.sm4) {
			return this.sm4;
		}

		Objects.requireNonNull(this.properties.getKey(), "秘钥[key]不能为空");

		this.lock.writeLock().lock();
		try {
			if (null != this.sm4) {
				return this.sm4;
			}
			this.sm4 = new SM4(this.properties.getKey().getBytes());
			return this.sm4;
		} finally {
			this.lock.writeLock().unlock();
		}
	}
}